package com.rslai.base.tool.servertest.command;

import com.rslai.base.tool.servertest.context.Context;
import com.rslai.base.tool.servertest.database.DbUnitWrapper;
import com.rslai.base.tool.servertest.model.KeyValueStore;
import com.rslai.base.tool.servertest.response.Response;
import com.rslai.base.tool.servertest.util.CloneUtil;
import com.rslai.base.tool.servertest.util.KeyValueUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.dbunit.Assertion;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.FilteredDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableMetaData;
import org.dbunit.dataset.filter.DefaultColumnFilter;
import org.dbunit.dataset.filter.ExcludeTableFilter;

public class CompareDatabaseStepCommand extends ParameterizedCommand {
    private String database;
    private String expected;
    private String replaceTableName;
    private String ignore;
    private Map<String, List<String>> ignoreColumns;

    public CompareDatabaseStepCommand(List<KeyValueStore> params) {
        super(params);
    }

    protected Response doExecuteInternal(Response preResult, List<KeyValueStore> processedParams, Context context)
            throws Throwable {
        this.database = KeyValueUtil.getValueByKey("database", processedParams);
        this.expected = KeyValueUtil.getValueByKey("expected", processedParams);
        this.replaceTableName = KeyValueUtil.getValueByKey("replaceTableName", processedParams);
        this.ignore = KeyValueUtil.getValueByKey("ignore", processedParams);
        computeIgnore();
        compare();

        return preResult;
    }

    private void computeIgnore() {
        this.ignoreColumns = new HashMap();
        if (StringUtils.isBlank(this.ignore)) return;
        String[] tables = StringUtils.split(this.ignore, ";");
        for (String table : tables) {
            String temp = StringUtils.trim(table);
            if (!StringUtils.isBlank(temp))
                if ((temp.contains("(")) && (temp.endsWith(")"))) {
                    int index = temp.indexOf("(");
                    String tableName = temp.substring(0, index);
                    String columnStr = temp.substring(index + 1, temp.length() - 1);
                    String[] columns = StringUtils.split(columnStr, ",");
                    List columnList = (List) this.ignoreColumns.get(tableName);
                    if (columnList == null) {
                        columnList = new ArrayList();
                        this.ignoreColumns.put(tableName, columnList);
                    }
                    columnList.addAll(Arrays.asList(columns));
                } else if (!this.ignoreColumns.containsKey(temp)) {
                    this.ignoreColumns.put(temp, null);
                }
        }
    }

    private void compare() throws Throwable {
        DbUnitWrapper dbunit = new DbUnitWrapper(this.database);
        IDataSet expectedDataSet = getExpectedDataSet(dbunit);
        IDataSet actualDataSet = dbunit.fetchDatabaseDataSet();
        compare(actualDataSet, expectedDataSet);
    }

    private void compare(IDataSet actualDataSet, IDataSet expectedDataSet) throws Throwable {
        String[] expectedTableNames = expectedDataSet.getTableNames();
        if (expectedTableNames.length == 0) return;
        String[] actualTableNames = actualDataSet.getTableNames();
        compareTableSize(actualTableNames, expectedTableNames);

        for (int i = 0; i < expectedTableNames.length; i++) {
            String tableName = expectedTableNames[i];
            ITable expectedTable = expectedDataSet.getTable(tableName);

            String[] ignoreColumns = getIgnoreColumns(tableName);
            if ((ignoreColumns != null) && (ignoreColumns.length != 0)) {
                expectedTable = DefaultColumnFilter.excludedColumnsTable(expectedTable, ignoreColumns);
            }

            ITable actualTable = actualDataSet.getTable(tableName);
            Column[] expectedColumns = expectedTable.getTableMetaData().getColumns();

            actualTable = DefaultColumnFilter.includedColumnsTable(actualTable, expectedColumns);

            Assertion.assertEquals(expectedTable, actualTable);
        }
    }

    private IDataSet getExpectedDataSet(DbUnitWrapper dbunit) {
        IDataSet dataSet = dbunit.generateDataSet(this.expected, this.replaceTableName, false);
        return new FilteredDataSet(new ExcludeTableFilter(getIgnoreTableNames()), dataSet);
    }

    private String[] getIgnoreTableNames() {
        List result = new ArrayList();
        Map.Entry entry;
        for (Iterator i$ = this.ignoreColumns.entrySet().iterator(); i$.hasNext();
             result.add(entry.getKey())) {
            entry = (Map.Entry) i$.next();
            if ((entry.getValue() != null) && (((List) entry.getValue()).size() != 0)) ;
        }
        return (String[]) result.toArray(new String[0]);
    }

    private String[] getIgnoreColumns(String tableName) {
        List columns = (List) this.ignoreColumns.get(tableName);
        if ((columns == null) || (columns.size() == 0)) return null;
        return (String[]) columns.toArray(new String[0]);
    }

    private void compareTableSize(String[] actualTableNames, String[] expectedTableNames) {
        if (expectedTableNames.length > actualTableNames.length) {
            String message = String.format("Expected include these tables: %s, but actual got: %s", new Object[]{inANotInB(expectedTableNames, actualTableNames), StringUtils.join(actualTableNames, ",")});

            throw new AssertionError(message);
        }
    }

    private String inANotInB(String[] a, String[] b) {
        List result = new ArrayList();
        for (String aItem : a) {
            for (String bItem : b) {
                if (aItem.equals(bItem)) ;
            }
            result.add(aItem);
        }
        return StringUtils.join(result, ",");
    }

    protected StepCommand doClone() {
        return new CompareDatabaseStepCommand(CloneUtil.cloneKeyValueStore(this.params));
    }

    public Map<String, Object> toReport() {
        Map details = new HashMap();
        details.put("stepName", "数据库比对:");
        details.put("name", String.format("对数据库%s进行比对", new Object[]{this.database}));

        ArrayList params = new ArrayList();
        params.add(new KeyValueStore("database", this.database));
        params.add(new KeyValueStore("expected", this.expected));
        params.add(new KeyValueStore("忽略的表和列", this.ignoreColumns));
        details.put("params", params);

        return details;
    }
}
